/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
/*
 * Created on Jun 20, 2008
 */

package com.bigdata.relation.accesspath;


/**
 * A thread-safe buffer backed by a fixed capacity array. Concrete
 * implementations must empty the buffer in {@link #flush(int, Object[])}.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
abstract public class AbstractArrayBuffer<E> implements IBuffer<E> {

//    protected static final Logger log = Logger.getLogger(AbstractArrayBuffer.class);
//    
//    protected static final boolean INFO = log.isInfoEnabled();
//
//    protected static final boolean DEBUG = log.isDebugEnabled();

    /**
     * The capacity of the backing buffer.
     */
    protected final int capacity;
    
    /**
     * The component type of the backing byte, used when a new instance is
     * allocated.
     */
    protected final Class cls;
    
    /**
     * An optional filter for keeping elements out of the buffer.
     */
    protected final IElementFilter<E> filter;
    
    protected int size;
    protected E[] buffer;

    /**
     * @param capacity
     *            The capacity of the backing buffer.
     * @param cls
     *            Array instances of this component type will be allocated.
     * @param filter
     *            An optional filter for keeping elements out of the buffer.
     */
    protected AbstractArrayBuffer(final int capacity, final Class cls,
            final IElementFilter<E> filter) {
        
        if (capacity <= 0)
            throw new IllegalArgumentException();

        if (cls == null)
            throw new IllegalArgumentException();
        
        this.capacity = capacity;

        this.cls = cls;
        
        this.filter = filter;

    }
    
    /**
     * If {@link #size()} reports zero(0).
     */
    public boolean isEmpty() {

        return size == 0;
        
    }

    /**
     * The approximate #of elements in the buffer.
     */
    public int size() {

        return size;
        
    }

    /**
     * Filters elements allowed into the buffer.
     * 
     * @param e
     *            Some element.
     * 
     * @return <code>true</code> iff the buffer accepts the element.
     */
    protected boolean accept(final E e) {

        if (filter != null) {

            if(!filter.isValid(e)) {
                
                // rejected by the filter.
                
//                if(DEBUG) {
//                    
//                    log.debug("rejected: element="+e+", filter="+filter);
//                    
//                }

                return false;

            }
            
        }
        
        return true;

    }

    @SuppressWarnings("unchecked")
    public void add(final E e) {

        if (e == null)
            throw new IllegalArgumentException();

//        if(DEBUG) {
//            
//            log.debug("element="+e);
//            
//        }
        
        if (accept(e)) {

            synchronized (this) {

                if (buffer == null) {

                    buffer = (E[]) java.lang.reflect.Array.newInstance(cls,
                            capacity);

                } else if (size == buffer.length) {

                    flush();

                }

//                try {
                    buffer[size++] = e;
//                } catch (ArrayStoreException ex) {
//                    throw new RuntimeException("bufferClass="
//                            + buffer.getClass()
//                            + "["
//                            + buffer.getClass().getComponentType()
//                            + "]"
//                            + ", e.class="
//                            + e.getClass()
//                            + (e.getClass().getComponentType() != null ? "["
//                                    + e.getClass().getComponentType() + "]"
//                                    : ""));
//                }

            }

        }

    }

    synchronized public long flush() {

        if (size > 0) {

//            if (INFO) {
//
//                log.info("flushing buffer with " + size + " elements");
//                
//            }
            
            final long nwritten = flush(size, buffer);
            
            counter += nwritten;
            
//            if (INFO) {
//
//                log.info("wrote " + nwritten + " elements, cumulative total="
//                        + counter);
//                
//            }
            
            clearBuffer();
            
        }
        
        return counter;
    
    }
    
    private long counter = 0L;
    
    synchronized public void reset() {
        
//        if(INFO) {
//            
//            log.info("Resetting buffer state and counter.");
//            
//        }
        
        clearBuffer();
        
        counter = 0L;
        
    }
    
    /** Clear hard references from the buffer for better GC. */
    private void clearBuffer() {

        for (int i = 0; i < size; i++) {
            
            buffer[i] = null;
            
        }

        // the buffer is now empty.
        size = 0;

    }

    /**
     * This method is automatically invoked if the buffer is flushed and it is
     * non-empty. The implementation is required to dispose of the contents of
     * the buffer. The caller is already synchronized on <i>this</i> so no
     * further synchronization is necessary. It is assumed that the contents of
     * the buffer have been safely disposed of when this method returns.
     * 
     * @param n
     *            The #of elements in the array.
     * @param a
     *            The array of elements.
     * 
     * @return The #of elements that were modified in the backing relation when
     *         the buffer was flushed (unlike {@link #flush()}, this is not a
     *         cumulative counter, but the #of modified elements in the relation
     *         for this operation only).
     */
    abstract protected long flush(int n, E[] a);
    
}
